#include "ExplosionTest.h"
#include "math/Vector2.h"
#include "TestManager.h"

#include <cassert>
#include <cmath>

using namespace phynet;

const Real ExplosionTest::PARTICLE_STARTING_SPEED = 20.0f;
const Real ExplosionTest::PARTICLE_STARTING_ANGLE_GRAD = 45.0f;
const Real ExplosionTest::PARTICLE_STARTING_DAMPING = 0.999f;
const Real ExplosionTest::PARTICLE_STARTING_EXPLOSION_SPEED = 5.0f;
const Real ExplosionTest::PARTICLE_MIN_FRAGMENT_MASS = 0.1f;

ExplosionTest::ExplosionTest(TestManager* testManager)
	: Test(testManager)
	, m_gravityFG(math::Vector2(0.0f, -9.81f))
{
	srand(NULL);
	Init();
}

ExplosionTest::~ExplosionTest()
{
}

void ExplosionTest::Init()
{
	m_engine.Init();

	m_nextParticleSpeed = PARTICLE_STARTING_SPEED;
	m_nextParticleAngleGrad = PARTICLE_STARTING_ANGLE_GRAD;
	m_nextParticleDamping = PARTICLE_STARTING_DAMPING;
	m_nextParticleMaxMassUnit = PARTICLE_STARTING_MAX_MASS_UNIT;
	m_explosionMaxSpeed = PARTICLE_STARTING_EXPLOSION_SPEED;

	m_engine.GetWorld().AddForce(&m_gravityFG);
	m_engine.GetWorld().AddForce(&m_windFG);

	Shoot();
}

void ExplosionTest::Shoot()
{
	m_windFG.Set(math::Vector2( (10-rand()%21)/10.0f, (10-rand()%21)/10.0f), 0.0f); //constant wind
	//m_spotGravityFG.Set( (10 + rand()%21)*1.0f, Vector2(0.0f, 10.0f), 100.0f);

	const Real angleRad = m_nextParticleAngleGrad * 3.1415926f / 180.0f;
	const math::Vector2 startingVel(m_nextParticleSpeed*cosf(angleRad), m_nextParticleSpeed*sinf(angleRad));
	const int randomSize = (1 + rand()%m_nextParticleMaxMassUnit);
	const Real particleMass = PARTICLE_MIN_FRAGMENT_MASS * static_cast<Real>(randomSize);
	const Real particleRadius = PARTICLE_MIN_FRAGMENT_MASS * sqrt(static_cast<Real>(randomSize));

	phy::Particle2D* newParticle = new phy::Particle2D(particleMass, particleRadius, m_nextParticleDamping, 1.0f, math::Vector2(-15.0f, 0.0f), startingVel); //mass, radius, damping, position, velocity...
	m_engine.GetWorld().AddParticle(newParticle);
	m_engine.GetWorld().AddForceOnParticle(&m_gravityFG, newParticle);
	m_engine.GetWorld().AddForceOnParticle(&m_windFG, newParticle);
}

void ExplosionTest::Explode(const Real i_maxRelativeSpeed)
{
	phy::Particles2D explodingParticles = m_engine.GetWorld().GetParticles();
	for(size_t i=0; i<explodingParticles.size(); ++i)
		if(explodingParticles[i]->GetMass() > PARTICLE_MIN_FRAGMENT_MASS)
			ExplodeParticle(explodingParticles[i], i_maxRelativeSpeed);
}

void ExplosionTest::ExplodeParticle(phy::Particle2D* const i_particle, const Real i_maxRelativeSpeed)
{
	int toBePartitioned = static_cast<int>(i_particle->GetMass()/PARTICLE_MIN_FRAGMENT_MASS);
	const int numFragment = 2 + rand()%(1+toBePartitioned/3);
	phy::Particles2D fragments(numFragment);
	
	phy::ParticleWorld2D& world = m_engine.GetWorld();

	math::Vector2 totalMomentum;
	for(int i=0; i<numFragment; ++i)
	{
		fragments[i] = new phy::Particle2D;

		int fragmentSize = toBePartitioned;
		math::Vector2 fragmentRelativeVelocity = -totalMomentum / (static_cast<Real>(fragmentSize)*PARTICLE_MIN_FRAGMENT_MASS);

		if(i+1 != numFragment)
		{
			fragmentSize = 1 + rand()%(toBePartitioned - (numFragment-i-1));
			const Real fragmentSpeed = (rand()%21) * (i_maxRelativeSpeed/20.f);
			const Real fragmentAngleGrad = static_cast<Real>(rand()%360);
			const Real angleRad = fragmentAngleGrad * 3.1415926f / 180.0f;
			fragmentRelativeVelocity.Set(fragmentSpeed*cosf(angleRad), fragmentSpeed*sinf(angleRad));
		}

		const Real fragmentMass = PARTICLE_MIN_FRAGMENT_MASS * static_cast<Real>(fragmentSize);
		const Real fragmentRadius = PARTICLE_MIN_FRAGMENT_MASS * sqrt(static_cast<Real>(fragmentSize));
		fragments[i]->Init(fragmentMass, fragmentRadius, i_particle->GetDamping(), i_particle->GetElasticity()/2.0f, i_particle->GetPosition(), i_particle->GetVelocity() + fragmentRelativeVelocity);
		
		toBePartitioned -= fragmentSize;
		totalMomentum += fragmentRelativeVelocity * fragmentMass;

		world.AddParticle(fragments[i]);
		world.AddForceOnParticle(&m_gravityFG, fragments[i]);
		world.AddForceOnParticle(&m_windFG, fragments[i]);
	}
	world.RemoveParticle(i_particle);
	delete i_particle;
}

void ExplosionTest::Render(Renderer& renderer) const
{
	typedef math::Vector2 Vector2;
	typedef phy::Particles2D Particles;

	const phy::ParticleWorld2D& world = m_engine.GetWorld();
	const Real speed = m_testManager->GetSlowMotion();

	// information
	char buffer[100];
	sprintf(buffer, "Ciao!! e' passato: %.2f  velocita' %.2fX", world.GetTime(), speed);
	renderer.RenderTextLine(buffer);
	sprintf(buffer, "Num Particles %d", world.GetParticles().size());
	renderer.RenderTextLine(buffer);
	Real totalMass = 0.0f;
	for(size_t i=0; i<world.GetParticles().size(); ++i)
		totalMass += world.GetParticles().at(i)->GetMass();
	sprintf(buffer, "TotalMass %.2f", totalMass);
	renderer.RenderTextLine(buffer);

	// instruction
	sprintf(buffer, "");
	renderer.RenderTextLine(buffer);
	sprintf(buffer, "Speed %.2f (-V/+B)", m_nextParticleSpeed);
	renderer.RenderTextLine(buffer);
	sprintf(buffer, "Direction %.2f (-C/+X)", m_nextParticleAngleGrad);
	renderer.RenderTextLine(buffer);
	sprintf(buffer, "Damping %.3f (-K/+L)", m_nextParticleDamping);
	renderer.RenderTextLine(buffer);
	sprintf(buffer, "Explosion Speed %.2f (-F/+G)", m_explosionMaxSpeed);
	renderer.RenderTextLine(buffer);
	sprintf(buffer, "Max Particle Mass %.2f (-H/+J)", m_nextParticleMaxMassUnit*PARTICLE_MIN_FRAGMENT_MASS); 
	renderer.RenderTextLine(buffer);
	sprintf(buffer, "Shoots a new particle! (S)");
	renderer.RenderTextLine(buffer);
	sprintf(buffer, "Explode current particles!! (D)");
	renderer.RenderTextLine(buffer);
	sprintf(buffer, "");
	renderer.RenderTextLine(buffer);
	sprintf(buffer, "Particle-Particle Collisions %s - (toggle Y)", (m_engine.IsParticleCollisionsEnabled() ? "enabled" : "disabled"));
	renderer.RenderTextLine(buffer);
	sprintf(buffer, "Particle-World Collisions DISABLED");
	renderer.RenderTextLine(buffer);
	sprintf(buffer, "");
	renderer.RenderTextLine(buffer);

	// axis
	renderer.RenderLine(Vector2(-50.0f, 0.0f), Vector2(50.0f, 0.0f));
	renderer.RenderLine(Vector2(0.0f, -50.0f), Vector2(0.0f, 50.0f));

	// cannon
	Real angleRad = m_nextParticleAngleGrad * 3.1415926f / 180.0f;
	Vector2 direct(cosf(angleRad), sinf(angleRad));
	renderer.RenderLine(Vector2(-15.0f, 0.0f), Vector2(-15.0f, 0.0f) + direct);

	// wind
	renderer.RenderLine(Vector2(0.1f, 0.1f), Vector2(0.1f, 0.1f) + m_windFG.GetBaseWindForce()*5.0f);

	// spot gravity
	//renderer.RenderCircle(m_spotGravityFG.GetLocation(), m_spotGravityFG.GetMass()/100.0f, 20);

	// world
	const Particles& particles = world.GetParticles();
	for (Particles::const_iterator i = particles.begin(); i != particles.end(); ++i)
		renderer.RenderCircle((*i)->GetPosition(), (*i)->GetRadius(), 8);
}

void ExplosionTest::Update(float i_elapsedTime)
{
	m_engine.Update(i_elapsedTime);

	phy::ParticleWorld2D& world = m_engine.GetWorld();

	for(size_t i=0; i<world.GetParticles().size(); ++i)
	{
		phy::Particle2D* const theParticle = world.GetParticles().at(i);
		const math::Vector2& particlePos = theParticle->GetPosition();
		if(particlePos.X() < -50.0f || particlePos.X() > 50.0f || particlePos.Y() < -0.5f || particlePos.Y() > 50.0f)
		{
			world.RemoveParticle(theParticle);
			delete theParticle;
		}
	}
}

void ExplosionTest::OnKeyPressed(int keyCode)
{
	if (keyCode == sf::Key::S)
	{
		Shoot();
	}
	else if (keyCode == sf::Key::D)
	{
		Explode(m_explosionMaxSpeed);
	}
	else if (keyCode == sf::Key::X)
	{
		m_nextParticleAngleGrad += 1.0f;
	}
	else if (keyCode == sf::Key::C)
	{
		m_nextParticleAngleGrad -= 1.0f;
	}
	else if (keyCode == sf::Key::B)
	{
		if(m_nextParticleSpeed < 30.0f)
			m_nextParticleSpeed += 1.0f;
	}
	else if (keyCode == sf::Key::V)
	{
		if(m_nextParticleSpeed >= 2.0f)
			m_nextParticleSpeed -= 1.0f;
	}
	else if (keyCode == sf::Key::F)
	{
		if(m_explosionMaxSpeed >= 1.0f)
			m_explosionMaxSpeed -= 0.5f;
	}
	else if (keyCode == sf::Key::G)
	{
		if(m_explosionMaxSpeed < 50.0f)
			m_explosionMaxSpeed += 0.5f;
	}
	else if (keyCode == sf::Key::H)
	{
		if(m_nextParticleMaxMassUnit >= 20)
			m_nextParticleMaxMassUnit -= 10;
	}
	else if (keyCode == sf::Key::J)
	{
		if(m_nextParticleMaxMassUnit < 1000)
			m_nextParticleMaxMassUnit += 10;
	}
	else if (keyCode == sf::Key::L)
	{
		if(math::LessThan(m_nextParticleDamping,1.0f))
			m_nextParticleDamping += 0.001f;
	}
	else if (keyCode == sf::Key::K)
	{
		m_nextParticleDamping -= 0.001f;
	}
	else if (keyCode == sf::Key::Y)
	{
		m_engine.EnableParticleCollisions(!m_engine.IsParticleCollisionsEnabled());		
	}
}
